#include <iostream>
#include <cstdio>
#include <vector>
#include <string>
#include <cstring>
#include <pthread.h>
#include <unistd.h>
#include <memory>
#include "Thread.hpp"
#include "Mutex.hpp"

using namespace std;

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;// 锁lock本身就是共享资源，所有线程都可以看到同一份地址空间中的共享资源lock
//锁定义成局部的或全局的都可以，在定义的时候可以进行锁的初始化。如果一个锁是局部的，你就必须用init和destroy来进行初始化和销毁。
//但如果该锁是全局的或静态的，可以直接用PTHREAD_MUTEX_INITIALIZER来进行初始化，不需要调用init来进行初始化


// 共享资源: 火车票
int tickets = 10000;

class ThreadData
{
public:
    ThreadData(const string &threadname, pthread_mutex_t *mutex_p)
        :_threadname(threadname)
        ,_mutex_p(mutex_p)
    {}
    
    ~ThreadData(){}

public:
    string _threadname;
    pthread_mutex_t *_mutex_p;
};


// 1.想要看到出问题的现象，就需要让多个线程交叉的进行执行，不要让线程串行的执行！
// 让多个线程交叉执行的本质: 让调度器尽可能的频繁发生线程的调度与切换！
// 2.线程什么时候被调度与切换呢？时间片到了、来了更高优先级的线程、线程阻塞等待的时候(线程不执行代码，将自己挂起阻塞等待的时候)...
// 3.线程一般是在什么时候检测上面的问题呢？从内核态返回到用户态的时候，线程要对调度状态进行检测，如果可以，就会发生线程切换
// (检测的过程，通过执行虚拟地址空间的3-4G内核代码来直接检测PCB里面的状态就行，可以判断出线程是否需要被切换)
// 线程切换其实就是通过执行内核空间的代码，执行内核空间代码就需要内核态的线程来完成，让CPU调度的基本单位PCB发生变化就行。
void *getTicket(void *args)
{
    // string username = static_cast<const char *>(args);
    ThreadData *td = static_cast<ThreadData *>(args);

    while(true)
    {
        // pthread_mutex_lock(td->_mutex_p);//锁定互斥锁
        // pthread_mutex_lock(&lock);//申请两次互斥锁
        // pthread_mutex_lock(&lock);
        {
            LockGuard lockguard(&lock);//RAII风格的加锁
            if(tickets > 0)//需要在有票的前提下
            {
                usleep(1234);// 1秒 = 10^3毫秒 = 10^6微秒 = 10^9纳秒  //用这段时间来模拟真实的抢票要花费的时间
                // cout << username << " 用户正在进行抢票: " << tickets-- << endl;
                cout << td->_threadname << " 用户正在进行抢票: " << tickets-- << endl;

                pthread_mutex_unlock(td->_mutex_p);//解锁互斥锁
            }
            else//加锁之后有可能条件不满足直接break，导致没有解锁互斥锁 
            {
                pthread_mutex_unlock(td->_mutex_p);
                break;
            }
            // pthread_mutex_unlock(td->_mutex_p); // 不能把解锁放在这里，因为条件不满足时，break可能会跳过解锁互斥锁的过程。

            //蛋哥的打印现象是只有一个线程在一直疯狂的抢票，就是因为那个线程的优先级高，线程一直被调度不被切换下来，在访问临界资源的过程中，这个线程操作也是原子性的
            //但我的打印现象还行，并不会出现一直调度一个线程的情况，加锁之后确实临界资源的访问安全了，不会出现票数为负数的情况。
        }

        usleep(1000);// 形成订单给用户，在这个时间里，让线程阻塞一下，这样能够让其他线程也能申请到锁，也能抢票
    }

    return nullptr;
}

int main()
{

    
    // version3:
    pthread_mutex_t lock;
    pthread_mutex_init(&lock, nullptr);
#define NUM 4
    vector<pthread_t> tids(NUM, 0);

    for(int i = 0; i < 4; i++)
    {
        char buffer[64];
        snprintf(buffer, sizeof(buffer), "thread %d", i+1);
        ThreadData *td = new ThreadData(buffer, &lock);
        
        pthread_create(&tids[i], nullptr, getTicket, td);
    }
    for(const pthread_t &tid : tids)
    {   
        pthread_join(tid, nullptr);
    }
    pthread_mutex_destroy(&lock);



    // version2:
    // static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; // 全局或静态锁可以不用init初始化和destroy销毁。

    // pthread_t t1, t2, t3, t4;
    // //需要把锁和线程的名字都传递给线程使用，所以我们可以直接搞一个结构体出来！
    // pthread_create(&t1, nullptr, getTicket, (void *)"thread 1");
    // pthread_create(&t2, nullptr, getTicket, (void *)"thread 2");
    // pthread_create(&t3, nullptr, getTicket, (void *)"thread 3");
    // pthread_create(&t4, nullptr, getTicket, (void *)"thread 4");

    // pthread_join(t1, nullptr);
    // pthread_join(t2, nullptr);
    // pthread_join(t3, nullptr);
    // pthread_join(t4, nullptr);





    // version1:
    // std::unique_ptr<Thread> thread1(new Thread(getTicket, (void *)"user1", 1));
    // std::unique_ptr<Thread> thread2(new Thread(getTicket, (void *)"user2", 2));
    // std::unique_ptr<Thread> thread3(new Thread(getTicket, (void *)"user3", 3));
    // std::unique_ptr<Thread> thread4(new Thread(getTicket, (void *)"user4", 4));
    
    // thread1->join();
    // thread2->join();
    // thread3->join();
    // thread4->join();

    
    return 0;
}